#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import logging
from traceback import format_exc
from os.path import abspath, dirname, join, isfile, isdir
from werkzeug import ClosingIterator
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError
from werkzeug.wrappers import BaseResponse
from werkzeug.contrib.sessions import SessionStore
from wrappers import AccostRequest
from handlers import Handler
__all__ = ['WSGIApplication']


def get_modules(prefix, parent):
    if not isfile(join(parent, '__init__.py')):
        return
    for pathname in os.listdir(parent):
        child = join(parent, pathname)
        if pathname.startswith('.'):
            continue
        elif pathname == '__init__.py':
            yield '.'.join(prefix)
        elif isdir(child):
            for i in get_modules(prefix+[pathname], child):
                yield i
        elif isfile(child) and pathname.endswith('.py'):
            yield '.'.join(prefix+[pathname[:-3]])


class WSGIApplication(object):
    def __init__(self, app):
        self.app = app
        p = abspath(dirname(app.__file__))
        # automatic get url_mapping
        views_path = join(p, 'views')
        for module in get_modules([app.__name__, 'views'], views_path):
            __import__(module)
        self.url_mapping = Map()
        for Klass in get_subclasses(Handler):
            if Klass.rule is not None:
                if not isinstance(Klass.rule, (list, tuple)):
                    rules = [Klass.rule]
                else:
                    rules = Klass.rule
                for rule in rules:
                    rule.endpoint = Klass
                    self.url_mapping.add(rule)
        if hasattr(app, 'mapping'):
            mapping = getattr(app, 'mapping')
            for rule in mapping:
                self.url_mapping.add(rule)
        self.url_mapping.update()
        self.db = getattr(app, 'db', {})
        self.debug = getattr(app, 'debug', True)
        self.session_store = getattr(app, 'session_store', None) or SessionStore()
        self.secret_key = getattr(app, 'secret_key',
                '\x80\x160m`6[\xff\xecK\x8c\xd8?h\x00Tsx_v')

    def __call__(self, environ, start_response):
#        start_response('200 OK', [('Content-type','text/plain')])
#        return ['Hello World!']
        adapter = self.url_mapping.bind_to_environ(environ)
        environ["accost.session_store"] = self.session_store
        environ["accost.secret_key"] = self.secret_key
        # build request with session
        request = AccostRequest(environ)
        # url mapping
        try:
            endpoint, kwargs = adapter.match()
            environ["accost.kwargs"] = kwargs
            # turn hander class path like 'www.views.main.index' to a real
            # handler class like index
            if isinstance(endpoint, basestring):
                dot = endpoint.rindex('.')
                module, cls = endpoint[:dot], endpoint[dot+1:]
                endpoint = __import__(module, {}, {}, [cls])
            if type(endpoint) is type and issubclass(endpoint, Handler):
                # build handler
                handler = endpoint()
                handler.app = self
                handler.errors = {}
                for store_name, store in self.db.items():
                    store.rollback()
                    setattr(handler, store_name, store)
                handler.request = request
                response = handler(kwargs)
                assert isinstance(response, BaseResponse)
            else:
                return ClosingIterator(endpoint(environ, start_response), self.cleanup)
        except HTTPException, e:
            logg = self.app.getSysLogger()
            import traceback
            logg.critical('-'*30 + '-'*30 )
            logg.critical(traceback.format_exc())
            logg.critical('extra_info:(url=%s)(cookie=%r)'%(request.url,request.cookies))
            logg.critical('-'*30 + '-'*30 )
            response = e
        except Exception, e:
            logg = self.app.getAppErrLogger()
            #logging.critical(e)
            import traceback
            logg.critical('-'*30 + '-'*30)
            logg.critical(traceback.format_exc())
            logg.critical('extra_info:(url=%s)(cookie=%r)'%(request.url,request.cookies))
            logg.critical('-'*30 + '-'*30)
            if self.debug:
                raise
            response = InternalServerError()
        request.finish(response)
        # do response
        return ClosingIterator(response(environ, start_response), self.cleanup)

    def cleanup(self):
        for session in getattr(self.app, "sqlalchemy_sessions", []):
            session.remove()
        for store_name, store in self.db.items():
            store.rollback()


class Mapping(object):
    def __init__(self):
        self._rules = []

    def expose(self, string, **kw):
        def _inner(app):
            rule = Rule(string, **kw)
            rule.endpoint = app
            self._rules.append(rule)
            return app
        return _inner

    def __iter__(self):
        for rule in self._rules:
            yield rule


def get_subclasses(Klass):
    for sub_class in Klass.__subclasses__():
        yield sub_class
        for sub_sub_class in get_subclasses(sub_class):
            yield sub_sub_class
